package main;

import java.util.ArrayList;
import java.util.List;

public class Individuo implements Comparable<Individuo> {
	/**
	 * Id do individuo
	 */
	int id;
	/**
	 * Lista de estacoes que o individuo cobre
	 */
	List<Boolean> stations;
	List<Integer> trueStations;
	/**
	 * Mundo em que o individuo esta contido
	 */
	World w;
	/**
	 * Custo associado ao individuo
	 * Soma do custo de todas as estacoes
	 */
	double custo;
	
	public String criador;
	boolean visitado;//true se individuo ja foi visitado para cruzamento
	
	public Individuo(){
		this.stations = new ArrayList<Boolean>();
		this.trueStations = new ArrayList<Integer>();
		this.visitado=false;
	}
	
	//seta id do individuo (serah inteiro sequencial para cada individuo gerado)
	public void setId(int id){
		this.id = id;
	}

	//recupera id do individuo
	public int getId(){
		return this.id;
	}
	
	//seta variavel vistado
	public void setVisitado(boolean v){
		this.visitado = v;
	}

	//recupera variavel vistado
	public boolean getVisitado(){
		return this.visitado;
	}
	
	//metodo que adiciona lista de estacoes que compoem o individuo
	public void setStations(List<Boolean> stations){
		
		this.stations = stations;
		
	}
	
	public void setTrueStations(List<Boolean> stations){
		this.trueStations = new ArrayList<Integer>();
		for (int i = 0; i <stations.size(); i++)
			if (stations.get(i)== true)
				this.trueStations.add(i);
	}
	
	public List<Integer> getTrueStations(){
		return this.trueStations;
	}
	
	//seta custo do individuo 
	public void setCusto(double d){
		this.custo = d;
	}
	
	public double getCusto(){
		return this.custo;
	}
	
	//gera e retorna um individuo
	public Individuo genIndividuo(World w, List<Boolean> points){

		//copia World
		World world = w;
		
		//copia pontos a serem cobertos, esta copia serah atualizada
		List<Boolean> aCobrir = points;
		
		
		//custo acumulado das estacoes selecionadas
		double custo = 0;
		//lista de inteiros em que cada indice i contem um inteiro i+1
		//que representa a estacao S(i+1) que ainda nao foi sorteada (antes das remocoes)
		List<Integer> naoSorteadas = new ArrayList<Integer>();

		for(int i=0; i<world.qtdEstacao; i++)
			naoSorteadas.add(i+1);

		
		//flag usada para verificar quantos pontos ainda nao foram cobertos
		int flag = aCobrir.size();

		
		//acumula "estacoes" (indice i == true se estacao S(i+1) foi selecionada) selecionadas ateh o momento
		List<Boolean> selecionadas = new ArrayList<Boolean>();

		for(int i=0; i<world.qtdEstacao;i++)
			selecionadas.add(i,false);

		
		//cria variavel Estacao que contera a estacao que queremos pegar a cada iteração do while
		Estacao alvo;
		
		//pega lista de estacoes do world
		List<Estacao> estacoes = world.estacoes;
		
		//variavel que contem numero da estacao sorteada
		int sorteada;
		
		//enquanto tiver pontos a cobrir (flag)
		while(flag > 0){

			//contara quantos pontos da estacao sorteada ja foram cobertos
			//por outra anteriormente
			int jaCoberto = 0;
			
			//contara quantos pontos a estacao sorteada cobre
			int alvoCobre = 0;
			
			sorteada = naoSorteadas.get((int)(Math.random() * (naoSorteadas.size())));
			
			//pega a estacao SX
			alvo = estacoes.get(sorteada - 1);
			
			
			//para cada ponto a se cobrir
			for(int j=0; j < aCobrir.size(); j++){
				//se estacao sorteada cobre aquele ponto
				if(alvo.pontos.get(j) == true){
					//e o ponto ainda nao foi coberto
					if(aCobrir.get(j) == true){
						//cobre o ponto e atualiza flag de controle do loop
						aCobrir.set(j, false);
						flag --;
					}
					//caso ponto ja tenha sido coberto, atualiza flag de controle
					else
						jaCoberto++;
				}
			}
			
			//conta qntos pontos a estacao sorteada cobre
			for(int i =0; i <alvo.pontos.size(); i++){
				if(alvo.pontos.get(i)==true)
					alvoCobre++;
			}
			//se a estacao sorteada cobriu algum ponto q ainda nao havia sido coberto
			if(alvoCobre != jaCoberto){
				//atualiza custo acumulado com o custo da estacao selecionada
				custo = custo + alvo.custo;
				//remove numero da estacao sorteada da lista de nao sorteadas
				naoSorteadas.remove((Integer)sorteada);
				//indice i que representa estacao S(i+1) torna-se true (estacao selecionada)
				selecionadas.set(sorteada - 1,true);
			}	
		}
		//retorna o individuo gerado
		this.setStations(selecionadas);
		this.setTrueStations(this.stations);
		this.setCusto(custo);
		//reseta pontos a cobrir
		for(int i = 0; i < aCobrir.size(); i++)
			aCobrir.set(i, true);
		
		return this;
	}
	

	//metodo para debug - imprime numero do individuo e estacoes q o compoem 
	//e seu custo
	public void printIndividuo(){
		String estacoes = new String("");
		for(int i=0; i< this.stations.size();i++){
			if(this.stations.get(i) == true){
				estacoes = estacoes + "S" + Integer.toString(i+1) + " ";
			}
		}
		System.out.println("Individuo "  + this.getId() + " com estacoes: ( " + estacoes + "), custo " + this.getCusto() +" e Criado por "+this.criador+".");
	}
	
	//metodo para debug - imprime numero do individuo e estacoes q o compoem 
	//e seu custo
	public void printIndividuo2(){
		String estacoes = new String("");
		for(int i=0; i< this.stations.size();i++){
			if(this.stations.get(i) == true){
				estacoes = estacoes + "S" + Integer.toString(i+1) + " ";
			}
		}
		System.out.println(estacoes + " " + this.getCusto());
	}
	
	/**
	 * Clona o Individuo
	 * Cria uma copia do individuo
	 */
	public Individuo clone(){
		//cria o novo individuo clone
		Individuo a = new Individuo();
		//seta id igual a este
		a.id = this.id;
		a.custo = this.custo;
		//copia cada estacao
		a.stations = new ArrayList<Boolean>();
		for(int i=0; i < this.stations.size(); i++){
			a.stations.add(new Boolean(this.stations.get(i).booleanValue()));
		}
		
		a.setTrueStations(a.stations);
		//retorna o clone
		return a;
	}
	
	/**
	 * Gera filho de dois individuos
	 * 
	 */
	public Individuo filho(Individuo p, int quebra){
		//cria o novo individuo clone
		Individuo a = new Individuo();
		
		//copia estacoes
		for(int i=0; i < quebra; i++){
			a.stations.add(new Boolean(this.stations.get(i).booleanValue()));
		}
		for(int i=quebra; i < this.stations.size(); i++){
			a.stations.add(new Boolean(p.stations.get(i).booleanValue()));
		}
		a.setTrueStations(a.stations);
		//retorna o filho
		return a;
	}	
	
	/**
	 * Gera filho de dois individuos fazendo or entre suas estacoes
	 * 
	 */
	public Individuo filhoOr(Individuo p){
		//cria o novo individuo clone
		Individuo a = new Individuo();
		
		
		//faz or dos bits dos 2 individuos
		for(int i=0; i < this.stations.size(); i++){
			a.stations.add(new Boolean(((this.stations.get(i).booleanValue()) || (p.stations.get(i).booleanValue()))));
		}
		
		a.setTrueStations(a.stations);
		//retorna o filho
		return a;
	}	
	
	/**
	 * tira estacoes de um individuo, mantendo sua validade e diminuindo o custo
	 * 
	 */
	public Individuo limpaStations(World w){
		int estacao,i=0;
		List<Integer> eTrue;
		eTrue=new ArrayList<Integer>();
		Individuo novo=new Individuo();
		
		eTrue = this.trueStations;
		novo=this.clone();
		while(eTrue.isEmpty()==false){
			//indice randomico a ser testado
			i=((int)(Math.random()*10000))%eTrue.size();
			estacao=eTrue.remove(i);
			novo.stations.set(estacao, false);
			if (!novo.isValid(w))
				novo.stations.set(estacao, true);
		}
		novo.setTrueStations(novo.stations);
		return novo;
	}
	
	public String toString(){
		return String.valueOf(id);
	}

	/**
	 * Verifica se o individuo eh valido
	 * @return
	 * true se o individuo for valido
	 * false se o individo eh invalido
	 */
	public boolean isValid(World w) {
		this.w = w;
		return isValid();
	}
	
	public boolean isValid() {
		List<Boolean> estacoes = this.stations;
		boolean[] vetor = new boolean[w.qtdPonto];
		for(int i=0; i < estacoes.size();i++){
			//se a estacao esta no individuo
			if(estacoes.get(i)){
				//para cada ponto coberto pela estacao
				List<Boolean> pontos = w.estacoes.get(i).pontos;
				for(int j=0;j<pontos.size();j++){
					if(pontos.get(j)){
						vetor[j] = pontos.get(j); 
					}
				}
			}
		}
		//verifica se ha pelo menos um ponto nao coberto no individuo
		for(int i=0; i <vetor.length;i++){
			if(!vetor[i])
				return false;
		}
		
		return true;
	}
	
	/**
	 * Calcula e seta o custo do individuo
	 * @param World
	 * Recebe o mundo para que seja possivel obter o custo das estacoes
	 * @return
	 * Somente seta o valor do individuo em questao
	 * 
	 */
	
	public void calculaCusto(World w){
		this.custo = 0;
		Boolean e;
		int tamanho = this.stations.size();
		for(int i=0; i < tamanho; i++){
			e = this.stations.get(i);
			if(e){
				this.custo += w.estacoes.get(i).custo;
			}
		}
	}

	@Override
	public int compareTo(Individuo arg0) {
		if(this.custo > arg0.custo)
			return 1;
		else if(this.custo < arg0.custo)
			return -1;
		else
			return 0;
	}
	
}
